package com.gitee.wsl.mathematics.geometry.d2.contour.region

import com.gitee.wsl.mathematics.geometry.d2.contour.ContourShape
import com.gitee.wsl.mathematics.geometry.d2.contour.ShapeContour
import com.gitee.wsl.mathematics.geometry.d2.contour.box.Box2
import com.gitee.wsl.mathematics.geometry.d2.contour.ring.Ring2
import com.gitee.wsl.mathematics.geometry.d2.contour.ring.toShapeContour
import com.gitee.wsl.mathematics.geometry.d2.contour.util.Clip
import com.gitee.wsl.mathematics.matrix.mat3.Mat3f
import com.gitee.wsl.mathematics.vector.vec2.Vec2f


class Region2(val rings: Array<Ring2>) {
    val bounds: Box2

    constructor(rings: Iterable<Ring2>) : this(
        rings.toList().toTypedArray()
    )

    fun test(p: Vec2f): Ring2.Result {
        for (r in rings) {
            val result: Ring2.Result = r.test(p)
            if (result.inside) {
                return if (result.curve == null && r.isClockwise) Ring2.Result.OUTSIDE else result
            }
        }
        return Ring2.Result.OUTSIDE
    }

    operator fun contains(p: Vec2f): Boolean {
        return test(p).inside
    }

    /// transforms and set operations
    fun transform(m: Mat3f): Region2 {
        return Region2(
            rings.map { r: Ring2 -> r.transform(m) }.toTypedArray()
        )
    }

    fun intersection(region: Region2): Region2 {
        return Clip.intersection(this, region)
    }

    fun union(region: Region2): Region2 {
        return Clip.union(this, region)
    }

    fun difference(region: Region2): Region2 {
        return Clip.difference(this, region)
    }

    companion object {
        fun of(vararg rings: Ring2): Region2 {
            return Region2(rings.toList())
        }
    }

    init {
        rings.sortBy { it.area }

        bounds = rings.
        map { r: Ring2 -> r.bounds }
            .fold(Box2.EMPTY) { obj, b -> obj.union(b) }
    }
}


internal fun Region2.toShape(): ContourShape {
    return if (rings.isEmpty()) {
        ContourShape.EMPTY
    } else {
        ContourShape(rings.map { it.toShapeContour() }.filter { !it.empty })
    }
}

internal fun Region2.toShapes(): List<ContourShape> {
    val shapes = mutableListOf<ContourShape>()
    if (rings.isNotEmpty()) {

        val contours = mutableListOf<ShapeContour>()
        rings.forEach { ring ->
            contours.add(ring.toShapeContour())

            if (!ring.isClockwise) {
                if (contours.isNotEmpty()) {
                    shapes.add(ContourShape(contours.reversed()))
                }
                contours.clear()
            }
        }
        if (contours.isNotEmpty()) {
            shapes.add(ContourShape(contours.reversed()))
        }

        if (rings.size != shapes.sumOf { it.contours.size }) {
            throw RuntimeException("conversion broken")
        }
    }
    return shapes
}

fun List<ContourShape>.toRegion2(): Region2 {
    return Region2(flatMap { shape ->
        shape.contours.map { it.ring2 }
    })
}